/*
 * $Id: Type1CFont.java,v 1.2 2007/12/20 18:33:32 rbair Exp $
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package com.sun.pdfview.font;

import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.NoninvertibleTransformException;
import java.io.IOException;

import com.sun.pdfview.PDFObject;

/**
 * A representation, with parser, of an Adobe Type 1C font.
 * @author Mike Wessler
 */
public class Type1CFont extends OutlineFont {
    String chr2name[]= new String[256];

    byte[] data;
    int pos;
    byte[] subrs;
    float[] stack= new float[100];
    int stackptr=0;

    String names[];
    int glyphnames[];
    int encoding[]= new int[256];
    
    String fontname;
    AffineTransform at= new AffineTransform(0.001f, 0, 0, 0.001f, 0, 0);

    int num;
    float fnum;
    int type;
    static int CMD=0;
    static int NUM=1;
    static int FLT=2;

    /**
     * create a new Type1CFont based on a font data stream and a descriptor
     * @param baseFont the postscript name of this font
     * @param src a stream containing the font
     * @param descriptor the descriptor for this font
     */
    public Type1CFont(String baseFont, PDFObject src,
                      PDFFontDescriptor descriptor) throws IOException {
	super(baseFont, src, descriptor);
        
        PDFObject dataObj = descriptor.getFontFile3();
        data= dataObj.getStream();
	pos= 0;
	parse();

	// TODO: free up (set to null) unused structures (data, subrs, stack)
    }

    /**
     * a debug method for printing the data
     */
    private void printData() {
	char[] parts= new char[17];
	int partsloc= 0;
	for (int i=0; i<data.length; i++) {
	    int d= ((int)data[i])&0xff;
	    if (d==0) {
		parts[partsloc++]= '.';
	    } else if (d<32 || d>=127) {
		parts[partsloc++]= '?';
	    } else {
		parts[partsloc++]= (char)d;
	    }
	    if (d<16) {
		System.out.print("0"+Integer.toHexString(d));
	    } else {
		System.out.print(Integer.toHexString(d));
	    }
	    if ((i&15)==15) {
		System.out.println("      "+new String(parts));
		partsloc= 0;
	    } else if ((i & 7)==7) {
		System.out.print("  ");
		parts[partsloc++]= ' ';
	    } else if ((i&1)==1) {
		System.out.print(" ");
	    }
	}
	System.out.println();
    }

    /**
     * read the next decoded value from the stream
     * @param charstring ????
     */
    private int readNext(boolean charstring) {
	num= (int)(data[pos++])&0xff;
	if (num==30 && !charstring) { // goofy floatingpoint rep
	    readFNum();
	    return type= FLT;
	} else if (num==28) {
	    num= (((int)data[pos])<<8)+(((int)data[pos+1])&0xff);
	    pos+= 2;
	    return type= NUM;
	} else if (num==29 && !charstring) {
	    num= (((int)data[pos]&0xff)<<24) |
		(((int)data[pos+1]&0xff)<<16) |
		(((int)data[pos+2]&0xff)<<8) |
		(((int)data[pos+3]&0xff));
	    pos+=4;
	    return type= NUM;
	} else if (num==12) {  // two-byte command
	    num= 1000+((int)(data[pos++])&0xff);
	    return type=CMD;
	} else if (num<32) {
	    return type=CMD;
	} else if (num<247) {
	    num-=139;
	    return type=NUM;
	} else if (num<251) {
	    num= (num-247)*256 + (((int)data[pos++])&0xff) + 108;
	    return type=NUM;
	} else if (num<255) {
	    num= -(num-251)*256 - (((int)data[pos++])&0xff) - 108;
	    return type=NUM;
	} else if (!charstring) { // dict shouldn't have a 255 code
	    printData();
	    throw new RuntimeException("Got a 255 code while reading dict");
	} else { // num was 255
	    fnum= ((((int)data[pos]&0xff)<<24) |
		   (((int)data[pos+1]&0xff)<<16) |
		   (((int)data[pos+2]&0xff)<<8) |
		   (((int)data[pos+3]&0xff)))/65536f;
	    pos+= 4;
	    return type=FLT;
	}
    }

    /**
     * read the next funky floating point number from the input stream.
     * value gets put into the fnum field.
     */
    public void readFNum() {
	// work in nybbles: 0-9=0-9, a=. b=E, c=E-, d=rsvd e=neg f=end
	float f=0;
	boolean neg= false;
	int exp= 0;
	int eval=0;
	float mul= 1;
	byte work= data[pos++];
	while (true) {
	    if (work==(byte)0xdd) {
		work= data[pos++];
	    }
	    int nyb= (work>>4) & 0xf;
	    work= (byte)((work<<4) | 0xd);
	    if (nyb<10) {
		if (exp!=0) {         // working on the exponent
		    eval= eval*10+nyb;
		} else if (mul==1) {  // working on an int
		    f= f*10+nyb;
		} else {              // working on decimal part
		    f+= nyb*mul;
		    mul/=10f;
		}
	    } else if (nyb==0xa) {    // decimal
		mul= 0.1f;
	    } else if (nyb==0xb) {    // E+
		exp= 1;
	    } else if (nyb==0xc) {    // E-
		exp= -1;
	    } else if (nyb==0xe) {      // neg
		neg= true;
	    } else {
		break;
	    }
	}
	fnum= (neg?-1:1)*f*(float)Math.pow(10, eval*exp);
    }

    /**
     * read an integer from the input stream
     * @param len the number of bytes in the integer
     * @return the integer
     */
    private int readInt(int len) {
	int n= 0;
	for (int i=0; i<len; i++) {
	    n= (n<<8)|(((int)data[pos++])&0xff);
	}
	return n;
    }

    /**
     * read the next byte from the stream
     * @return the byte
     */
    private int readByte() {
	return ((int)data[pos++])&0xff;
    }
	    

    // DICT structure:
    // operand operator operand operator ...

    // INDEX structure:
    // count(2) offsize [offset offset ... offset] data
    // offset array has count+1 entries
    // data starts at 3+(count+1)*offsize
    // offset for data is offset+2+(count+1)*offsize

    /**
     * get the size of the dictionary located within the stream at
     * some offset.
     * @param loc the index of the start of the dictionary
     * @return the size of the dictionary, in bytes.
     */
    public int getIndexSize(int loc) {
	//	System.out.println("Getting size of index at "+loc);
	int hold= pos;
	pos= loc;
	int count= readInt(2);
	if (count==0) {
	    return 2;
	}
	int encsz= readByte();
	// pos is now at the first offset.  last offset is at count*encsz
	pos+= count*encsz;
	int end= readInt(encsz);
	pos= hold;
	return 2+(count+1)*encsz+end;
    }

    /**
     * A range.  There's probably a version of this class floating around
     * somewhere already in Java.
     */
    class Range {
	private int start;
	private int len;
	public Range(int start, int len) {
	    this.start= start;
	    this.len= len;
	}
	public final int getStart() {return start;}
	public final int getLen() {return len;}
	public final int getEnd() {return start+len;}
    }

    /**
     * Get the range of a particular index in a dictionary.
     * @param index the start of the dictionary.
     * @param id the index of the entry in the dictionary
     * @return a range describing the offsets of the start and end of
     * the entry from the start of the file, not the dictionary
     */
    Range getIndexEntry(int index, int id) {
	int hold= pos;
	pos= index;
	int count= readInt(2);
	int encsz= readByte();
	pos+= encsz*id;
	int from= readInt(encsz);
	Range r= new Range(from+2+index+encsz*(count+1), readInt(encsz)-from);
	pos= hold;
	return r;
    }

    // Top DICT: NAME    CODE   DEFAULT
    // charstringtype    12 6    2
    // fontmatrix        12 7    0.001 0 0 0.001
    // charset           15      - (offset)  names of glyphs (ref to name idx)
    // encoding          16      - (offset)  array of codes
    // CharStrings       17      - (offset)
    // Private           18      - (size, offset)

    // glyph at position i in CharStrings has name charset[i]
    // and code encoding[i]

    int charstringtype= 2;
    float temps[]= new float[32];
    int charsetbase=0;
    int encodingbase=0;
    int charstringbase=0;
    int privatebase=0;
    int privatesize=0;
    int gsubrbase= 0;
    int lsubrbase= 0;
    int gsubrsoffset=0;
    int lsubrsoffset=0;

    int nglyphs=1;

    /**
     * read a dictionary that exists within some range, parsing the entries
     * within the dictionary.
     */
    private void readDict(Range r) {
	//	System.out.println("reading dictionary from "+r.getStart()+" to "+r.getEnd());
	pos= r.getStart();
	while(pos<r.getEnd()) {
	    int cmd= readCommand(false);
	    if (cmd== 1006) { // charstringtype, default=2
		charstringtype= (int)stack[0];
	    } else if (cmd== 1007) { // fontmatrix
		if (stackptr==4) {
		    at= new AffineTransform((float)stack[0], (float)stack[1],
					    (float)stack[2], (float)stack[3],
					    0, 0);
		} else {
		    at= new AffineTransform((float)stack[0], (float)stack[1],
					    (float)stack[2], (float)stack[3],
					    (float)stack[4], (float)stack[5]);
		}
	    } else if (cmd== 15) { // charset
		charsetbase= (int)stack[0];
	    } else if (cmd== 16) { // encoding
		encodingbase= (int)stack[0];
	    } else if (cmd== 17) { // charstrings
		charstringbase= (int)stack[0];
	    } else if (cmd== 18) { // private
		privatesize= (int)stack[0];
		privatebase= (int)stack[1];
	    } else if (cmd== 19) { // subrs (in Private dict)
		lsubrbase= (int)stack[0];
		lsubrsoffset= calcoffset(lsubrbase);
	    }
	    stackptr= 0;
	}
    }

    /**
     * read a complete command.  this may involve several numbers
     * which go onto a stack before an actual command is read.
     * @param charstring ????
     * @return the command.  Some numbers may also be on the stack.
     */
    private int readCommand(boolean charstring) {
	while(true) {
	    int t= readNext(charstring);
	    if (t== CMD) {
		/*
		System.out.print("CMD= "+num+", args=");
		for (int i=0; i<stackptr; i++) {
		    System.out.print(" "+stack[i]);
		}
		System.out.println();
		*/
		return num;
	    } else {
		stack[stackptr++]= (t==NUM)?(float)num:(float)fnum;
	    }
	}
    }

    /**
     * parse information about the encoding of this file.
     * @param base the start of the encoding data
     */
    private void readEncodingData(int base) {
	if (base==0) {  // this is the StandardEncoding
	    //	    System.out.println("**** STANDARD ENCODING!");
	    System.arraycopy(FontSupport.standardEncoding, 0, encoding, 0,
			     FontSupport.standardEncoding.length);
	} else if (base==1) {  // this is the expert encoding
	    System.out.println("**** EXPERT ENCODING!");
	    // TODO: copy ExpertEncoding
	} else {
	    pos= base;
	    int encodingtype= readByte();
	    if ((encodingtype&127)==0) {
		int ncodes= readByte();
		for(int i=1; i<ncodes+1; i++) {
		    int idx = readByte() & 0xff;
                    encoding[idx] = i;
		}
	    } else if ((encodingtype&127)==1) {
		int nranges= readByte();
		int p=1;
		for (int i=0; i<nranges; i++) {
		    int start= readByte();
		    int more= readByte();
		    for (int j=start; j<start+more+1; j++) {
			encoding[j]= p++;
		    }
		}
	    } else {
		System.out.println("Bad encoding type: "+encodingtype);
	    }
	    // TODO: now check for supplemental encoding data
	}
    }

    /**
     * read the names of the glyphs.
     * @param base the start of the glyph name table
     */
    private void readGlyphNames(int base) {
	if (base==0) {
	    glyphnames= new int[229];
	    for (int i=0; i<glyphnames.length; i++) {
		glyphnames[i]= i;
	    }
	    return;
	} else if (base==1) {
	    glyphnames= FontSupport.type1CExpertCharset;
	    return;
	} else if (base==2) {
	    glyphnames= FontSupport.type1CExpertSubCharset;
	    return;
	}
	// nglyphs has already been set.
	glyphnames= new int[nglyphs];
	glyphnames[0]= 0;
	pos= base;
	int t= readByte();
	if (t==0) {
	    for (int i=1; i<nglyphs; i++) {
		glyphnames[i]= readInt(2);
	    }
	} else if (t==1) {
	    int n= 1;
	    while(n<nglyphs) {
		int sid= readInt(2);
		int range= readByte()+1;
		for (int i=0; i<range; i++) {
		    glyphnames[n++]= sid++;
		}
	    }
	} else if (t==2) {
	    int n= 1;
	    while(n<nglyphs) {
		int sid= readInt(2);
		int range= readInt(2)+1;
		for (int i=0; i<range; i++) {
		    glyphnames[n++]= sid++;
		}
	    }
	}
    }

    /**
     * read a list of names
     * @param base the start of the name table
     */
    private void readNames(int base) {
	pos= base;
	int nextra= readInt(2);
	names= new String[nextra];
	//	safenames= new String[nextra];
	for (int i=0; i<nextra; i++) {
	    Range r= getIndexEntry(base, i);
	    names[i]= new String(data, r.getStart(), r.getLen());
	    //	    System.out.println("Read name: "+i+" from "+r.getStart()+" to "+r.getEnd()+": "+safe(names[i]));
	}
    }

    /**
     * parse the font data.
     * @param encdif a dictionary describing the encoding.
     */
    private void parse() throws IOException {
	int majorVersion= readByte();
	int minorVersion= readByte();
	int hdrsz= readByte();
	int offsize= readByte();
	// jump over rest of header: base of font names index
	int fnames= hdrsz;
	// offset in the file of the array of font dicts
	int topdicts= fnames + getIndexSize(fnames);
	// offset in the file of local names
	int theNames= topdicts + getIndexSize(topdicts);
	// offset in the file of the array of global subroutines
	gsubrbase= theNames + getIndexSize(theNames);
	gsubrsoffset= calcoffset(gsubrbase);
	// read extra names
	readNames(theNames);
	// does this file have more than one font?
	pos= topdicts;
	if (readInt(2)!=1) {
	    printData();
	    throw new RuntimeException("More than one font in this file!");
	}
	Range r= getIndexEntry(fnames, 0);
	fontname= new String(data, r.getStart(), r.getLen());
	// read first dict
	//	System.out.println("TOPDICT[0]:");
	readDict(getIndexEntry(topdicts, 0));
	// read the private dictionary
	//	System.out.println("PRIVATE DICT:");
	readDict(new Range(privatebase, privatesize));
	// calculate the number of glyphs
	pos= charstringbase;
	nglyphs= readInt(2);
	// now get the glyph names
	//	System.out.println("GLYPHNAMES:");
	readGlyphNames(charsetbase);
	// now figure out the encoding
	//	System.out.println("ENCODING:");
	readEncodingData(encodingbase);
    }

    /**
     * get the index of a particular name.  The name table starts with
     * the standard names in FontSupport.stdNames, and is appended by
     * any names in the name table from this font's dictionary.
     */
    private int getNameIndex(String name) {
	int val= FontSupport.findName(name, FontSupport.stdNames);
	if (val==-1) {
	    val= FontSupport.findName(name, names) + FontSupport.stdNames.length;
	}
	if (val==-1) {
	    val= 0;
	}
	return val;
    }

    /**
     * convert a string to one in which any non-printable bytes are
     * replaced by "<###>" where ## is the value of the byte.
     */
    private String safe(String src) {
	StringBuffer sb= new StringBuffer();
	for (int i=0; i<src.length(); i++) {
	    char c= src.charAt(i);
	    if (c>=32 && c<128) {
		sb.append(c);
	    } else {
		sb.append("<"+(int)c+">");
	    }
	}
	return sb.toString();
    }

    /**
     * Read the data for a glyph from the glyph table, and transform
     * it based on the current transform.
     *
     * @param base the start of the glyph table
     * @param offset the index of this glyph in the glyph table
     */
    private synchronized GeneralPath readGlyph(int base, int offset) {
	FlPoint pt = new FlPoint();
	
        // find this entry
	Range r= getIndexEntry(base, offset);
        
        // create a path
	GeneralPath gp= new GeneralPath();
	
        
        // rember the start position (for recursive calls due to seac)
        int hold = pos;
        
        // read the glyph itself
        stackptr= 0;
	parseGlyph(r, gp, pt);
        
        // restore the start position
        pos = hold;
        
    	gp.transform(at);
        
        return gp;
    }

    /**
     * calculate an offset code for a dictionary
     * @param base the index of the start of the dictionary
     */
    public int calcoffset(int base) {
	int len= getIndexSize(base);
	if (len<1240) {
	    return -107;
	} else if (len<33900) {
	    return -1131;
	} else {
	    return -32768;
	}
    }

    /**
     * get the name associated with an ID.
     * @param id the index of the name
     * @return the name from the FontSupport.stdNames table augmented
     * by the local name table
     */
    public String getSID(int id) {
	if (id<FontSupport.stdNames.length) {
	    return FontSupport.stdNames[id];
	} else {
	    id-= FontSupport.stdNames.length;
	    return names[id];
	}
    }

     /**
     * build an accented character out of two pre-defined glyphs.
     * @param x the x offset of the accent
     * @param y the y offset of the accent
     * @param b the index of the base glyph
     * @param a the index of the accent glyph
     * @param gp the GeneralPath into which the combined glyph will be
     * written.
     */
    private void buildAccentChar(float x, float y, char b, char a,
                                 GeneralPath gp) {        
        // get the outline of the accent
        GeneralPath pathA = getOutline(a, getWidth(a, null));
        
        // undo the effect of the transform applied in read 
        AffineTransform xformA = AffineTransform.getTranslateInstance(x, y);
        try {
            xformA.concatenate(at.createInverse());
        } catch (NoninvertibleTransformException nte) {
            // oh well ...
        }
        pathA.transform(xformA);
        
        GeneralPath pathB = getOutline(b, getWidth(b, null));
        
        try {
            AffineTransform xformB = at.createInverse();
            pathB.transform(xformB);
        } catch (NoninvertibleTransformException nte) {
            // ignore
        }
        
        gp.append(pathB, false);
        gp.append(pathA, false);        
    }
    
    /**
     * parse a glyph defined in a particular range
     * @param r the range of the glyph definition
     * @param gp a GeneralPath in which to store the glyph outline
     * @param pt a FlPoint representing the end of the current path
     */
    void parseGlyph(Range r, GeneralPath gp, FlPoint pt) {
        pos= r.getStart();
	int i;
	float x1,y1,x2,y2,x3,y3,ybase;
	int hold;
	int stemhints= 0;
	while (pos<r.getEnd()) {
	    int cmd= readCommand(true);
	    hold= 0;
	    switch (cmd) {
	    case 1: // hstem
	    case 3: // vstem
		stackptr= 0;
		break;
	    case 4: // vmoveto
		if (stackptr>1) {  // this is the first call, arg1 is width
		    stack[0]= stack[1];
		}
		pt.y+= stack[0];
		if (pt.open) {
		    gp.closePath();
		}
		pt.open= false;
		gp.moveTo(pt.x, pt.y);
		stackptr= 0;
		break;
	    case 5: // rlineto
		for (i=0; i<stackptr;) {
		    pt.x+= stack[i++];
		    pt.y+= stack[i++];
		    gp.lineTo(pt.x, pt.y);
		}
		pt.open= true;
		stackptr= 0;
		break;
	    case 6: // hlineto
		for (i=0; i<stackptr;) {
		    if ((i&1)==0) {
			pt.x+= stack[i++];
		    } else {
			pt.y+= stack[i++];
		    }
		    gp.lineTo(pt.x, pt.y);
		}
		pt.open= true;
		stackptr= 0;
		break;
	    case 7: // vlineto
		for (i=0; i<stackptr;) {
		    if ((i&1)==0) {
			pt.y+= stack[i++];
		    } else {
			pt.x+= stack[i++];
		    }
		    gp.lineTo(pt.x, pt.y);
		}
		pt.open= true;
		stackptr= 0;
		break;
	    case 8: // rrcurveto
		for (i=0; i<stackptr;) {
		    x1= pt.x+stack[i++];
		    y1= pt.y+stack[i++];
		    x2= x1+stack[i++];
		    y2= y1+stack[i++];
		    pt.x= x2+stack[i++];
		    pt.y= y2+stack[i++];
		    gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		}
		pt.open= true;
		stackptr= 0;
		break;
	    case 10: // callsubr
		hold= pos;
		i= (int)stack[--stackptr]+lsubrsoffset;
		Range lsubr= getIndexEntry(lsubrbase, i);
		parseGlyph(lsubr, gp, pt);
		pos= hold;
		break;
	    case 11: // return
		return;
	    case 14: // endchar
                // width x y achar bchar endchar == x y achar bchar seac 
                if (stackptr == 5) {
                    buildAccentChar(stack[1], stack[2], (char) stack[3],
                                    (char) stack[4], gp);
                }
		if (pt.open) {
		    gp.closePath();
		}
		pt.open= false;
		stackptr= 0;
		break;
	    case 18: // hstemhm
		stemhints+= stackptr/2;
		stackptr= 0;
		break;
	    case 19: // hintmask
	    case 20: // cntrmask
		stemhints+= stackptr/2;
		pos+= (stemhints-1)/8+1;
		stackptr= 0;
		break;
	    case 21: // rmoveto
		if (stackptr>2) {
		    stack[0]= stack[1];
		    stack[1]= stack[2];
		}
		pt.x+= stack[0];
		pt.y+= stack[1];
		if (pt.open) {
		    gp.closePath();
		}
		gp.moveTo(pt.x, pt.y);
		pt.open= false;
		stackptr= 0;
		break;
	    case 22: // hmoveto
		if (stackptr>1) {
		    stack[0]= stack[1];
		}
		pt.x+= stack[0];
		if (pt.open) {
		    gp.closePath();
		}
		gp.moveTo(pt.x, pt.y);
		pt.open= false;
		stackptr= 0;
		break;
	    case 23: // vstemhm
		stemhints+= stackptr/2;
		stackptr= 0;
		break;
	    case 24: // rcurveline
		for (i=0; i<stackptr-2;) {
		    x1= pt.x+stack[i++];
		    y1= pt.y+stack[i++];
		    x2= x1+stack[i++];
		    y2= y1+stack[i++];
		    pt.x= x2+stack[i++];
		    pt.y= y2+stack[i++];
		    gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		}
		pt.x+= stack[i++];
		pt.y+= stack[i++];
		gp.lineTo(pt.x, pt.y);
		pt.open= true;
		stackptr= 0;
		break;		
	    case 25: // rlinecurve
		for (i=0; i<stackptr-6;) {
		    pt.x+= stack[i++];
		    pt.y+= stack[i++];
		    gp.lineTo(pt.x, pt.y);
		}
		x1= pt.x+stack[i++];
		y1= pt.y+stack[i++];
		x2= x1+stack[i++];
		y2= y1+stack[i++];
		pt.x= x2+stack[i++];
		pt.y= y2+stack[i++];
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		pt.open= true;
		stackptr= 0;
		break;
	    case 26: // vvcurveto
		i=0;
		if ((stackptr & 1)==1) { // odd number of arguments
		    pt.x+= stack[i++];
		}
		while (i<stackptr) {
		    x1= pt.x;
		    y1= pt.y+stack[i++];
		    x2= x1+stack[i++];
		    y2= y1+stack[i++];
		    pt.x= x2;
		    pt.y= y2+stack[i++];
		    gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		}
		pt.open= true;
		stackptr= 0;
		break;
	    case 27: // hhcurveto
		i=0;
		if ((stackptr & 1)==1) { // odd number of arguments
		    pt.y+= stack[i++];
		}
		while (i<stackptr) {
		    x1= pt.x+stack[i++];
		    y1= pt.y;
		    x2= x1+stack[i++];
		    y2= y1+stack[i++];
		    pt.x= x2+stack[i++];
		    pt.y= y2;
		    gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		}
		pt.open= true;
		stackptr= 0;
		break;
	    case 29: // callgsubr
		hold= pos;
		i= (int)stack[--stackptr]+gsubrsoffset;
		Range gsubr= getIndexEntry(gsubrbase, i);
		parseGlyph(gsubr, gp, pt);
		pos= hold;
		break;
	    case 30: // vhcurveto
		hold= 4;
	    case 31: // hvcurveto
		for (i=0; i<stackptr;) {
		    boolean hv= (((i+hold)&4)==0);
		    x1= pt.x+(hv?stack[i++]:0);
		    y1= pt.y+(hv?0:stack[i++]);
		    x2= x1+stack[i++];
		    y2= y1+stack[i++];
		    pt.x= x2+(hv?0:stack[i++]);
		    pt.y= y2+(hv?stack[i++]:0);
		    if (i==stackptr-1) {
			if (hv) {
			    pt.x+= stack[i++];
			} else {
			    pt.y+= stack[i++];
			}
		    }
		    gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		}
		pt.open= true;
		stackptr= 0;
		break;
	    case 1000: // old dotsection command.  ignore.
		stackptr= 0;
		break;
	    case 1003: // and
		x1= stack[--stackptr];
		y1= stack[--stackptr];
		stack[stackptr++]= ((x1!=0) && (y1!=0))?1:0;
		break;
	    case 1004: // or
		x1= stack[--stackptr];
		y1= stack[--stackptr];
		stack[stackptr++]= ((x1!=0) || (y1!=0))?1:0;
		break;
	    case 1005: // not
		x1= stack[--stackptr];
		stack[stackptr++]= (x1==0) ? 1:0;
		break;
	    case 1009: // abs
		stack[stackptr-1]= Math.abs(stack[stackptr-1]);
		break;
	    case 1010: // add
		x1= stack[--stackptr];
		y1= stack[--stackptr];
		stack[stackptr++]= x1+y1;
		break;
	    case 1011: // sub
		x1= stack[--stackptr];
		y1= stack[--stackptr];
		stack[stackptr++]= y1-x1;
		break;
	    case 1012: // div
		x1= stack[--stackptr];
		y1= stack[--stackptr];
		stack[stackptr++]= y1/x1;
		break;
	    case 1014: // neg
		stack[stackptr-1]= -stack[stackptr-1];
		break;
	    case 1015: // eq
		x1= stack[--stackptr];
		y1= stack[--stackptr];
		stack[stackptr++]= (x1==y1) ? 1:0;
		break;
	    case 1018: // drop
		stackptr--;
		break;
	    case 1020: // put
		i= (int)stack[--stackptr];
		x1= stack[--stackptr];
		temps[i]= x1;
		break;
	    case 1021: // get
		i= (int)stack[--stackptr];
		stack[stackptr++]= temps[i];
		break;
	    case 1022: // ifelse
		if (stack[stackptr-2]>stack[stackptr-1]) {
		    stack[stackptr-4]= stack[stackptr-3];
		}
		stackptr-=3;
		break;
	    case 1023: // random
		stack[stackptr++]= (float)Math.random();
		break;
	    case 1024: // mul
		x1= stack[--stackptr];
		y1= stack[--stackptr];
		stack[stackptr++]= y1*x1;
		break;
	    case 1026: // sqrt
		stack[stackptr-1]= (float)Math.sqrt(stack[stackptr-1]);
		break;
	    case 1027: // dup
		x1= stack[stackptr-1];
		stack[stackptr++]= x1;
		break;
	    case 1028: // exch
		x1= stack[stackptr-1];
		stack[stackptr-1]= stack[stackptr-2];
		stack[stackptr-2]= x1;
		break;
	    case 1029: // index
		i= (int)stack[stackptr-1];
		if (i<0) i=0;
		stack[stackptr-1]= stack[stackptr-2-i];
		break;
	    case 1030: // roll
		i= (int)stack[--stackptr];
		int n= (int)stack[--stackptr];
		// roll n number by i (+ = upward)
		if (i>0) {
		    i= i%n;
		} else {
		    i= n - (-i % n);
		}
		// x x x x i y y y -> y y y x x x x i (where i=3)
		if (i>0) {
		    float roll[]= new float[n];
		    System.arraycopy(stack, stackptr-1-i, roll, 0, i);
		    System.arraycopy(stack, stackptr-1-n, roll, i, n-i);
		    System.arraycopy(roll, 0, stack, stackptr-1-n, n);
		}
		break;
	    case 1034: // hflex
		x1= pt.x+stack[0];
		y1= ybase= pt.y;
		x2= x1+stack[1];
		y2= y1+stack[2];
		pt.x= x2+stack[3];
		pt.y= y2;
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		x1= pt.x+stack[4];
		y1= pt.y;
		x2= x1+stack[5];
		y2= ybase;
		pt.x= x2+stack[6];
		pt.y= y2;
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		pt.open= true;
		stackptr= 0;
		break;
	    case 1035: // flex
		x1= pt.x+stack[0];
		y1= pt.y+stack[1];
		x2= x1+stack[2];
		y2= y1+stack[3];
		pt.x= x2+stack[4];
		pt.y= y2+stack[5];
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		x1= pt.x+stack[6];
		y1= pt.y+stack[7];
		x2= x1+stack[8];
		y2= y1+stack[9];
		pt.x= x2+stack[10];
		pt.y= y2+stack[11];
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		pt.open= true;
		stackptr= 0;
		break;
	    case 1036: // hflex1
		ybase= pt.y;
		x1= pt.x+stack[0];
		y1= pt.y+stack[1];
		x2= x1+stack[2];
		y2= y1+stack[3];
		pt.x= x2+stack[4];
		pt.y= y2;
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		x1= pt.x+stack[5];
		y1= pt.y;
		x2= x1+stack[6];
		y2= y1+stack[7];
		pt.x= x2+stack[8];
		pt.y= ybase;
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		pt.open= true;
		stackptr= 0;
		break;
	    case 1037: // flex1
		ybase= pt.y;
		float xbase= pt.x;
		x1= pt.x+stack[0];
		y1= pt.y+stack[1];
		x2= x1+stack[2];
		y2= y1+stack[3];
		pt.x= x2+stack[4];
		pt.y= y2+stack[5];
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		x1= pt.x+stack[6];
		y1= pt.y+stack[7];
		x2= x1+stack[8];
		y2= y1+stack[9];
		if (Math.abs(x2-xbase)>Math.abs(y2-ybase)) {
		    pt.x= x2+stack[10];
		    pt.y= ybase;
		} else {
		    pt.x= xbase;
		    pt.y= y2+stack[10];
		}
		gp.curveTo(x1,y1,x2,y2,pt.x,pt.y);
		pt.open= true;
		stackptr= 0;
		break;
	    default:
		System.out.println("ERROR! TYPE1C CHARSTRING CMD IS "+cmd);
		break;
	    }
	}
    }

     /**
     * Get a glyph outline by name
     *
     * @param name the name of the desired glyph
     * @return the glyph outline, or null if unavailable
     */
    protected GeneralPath getOutline(String name, float width) {
        // first find the index of this name
        int index = getNameIndex(name);
        
        // now find the glyph with that name
        for (int i = 0; i < glyphnames.length; i++) {
            if (glyphnames[i] == index) {
                return readGlyph(charstringbase, i);
            }
        }
        
        // not found -- return the unknown glyph
        return readGlyph(charstringbase, 0);
    }
    
    /**
     * Get a glyph outline by character code
     *
     * Note this method must always return an outline 
     *
     * @param src the character code of the desired glyph
     * @return the glyph outline
     */
    protected GeneralPath getOutline(char src, float width) {
        // ignore high bits
        int index = (int) (src & 0xff);
       
        // if we use a standard encoding, the mapping is from glyph to SID
        // therefore we must find the glyph index in the name table
        if (encodingbase == 0 || encodingbase == 1) {
            for (int i = 0; i < glyphnames.length; i++) {
                if (glyphnames[i] == encoding[index]) {
                    return readGlyph(charstringbase, i);
                }
            }
        } else {
            // for a custom encoding, the mapping is from glyph to GID, so
            // we can just map the glyph directly
            if (index > 0 && index < encoding.length) {
                return readGlyph(charstringbase, encoding[index]);
            }
        }
       
        // for some reason the glyph was not found, return the empty glyph
        return readGlyph(charstringbase, 0);
    }
}

